<?php
declare(strict_types=1);

namespace WebApp\lib\db\mysql;

use ErrorException;
use PDO;
use PDOException;
use txz\common\base\utils\LogHelper;
use txz\common\dao\base\mysql\CMysqlMgr;
use WebApp\lib\util\Util;

/**
 * Class NormalTable
 * @package WebApp\lib\db
 */
class NormalTable extends MysqlTable implements ITable
{
    /**
     * @param $arr
     * @return mixed
     */
    public function transaction($arr)
    {
        return $func = function (IMysqlExecute $exe) use (&$arr) {
            $pdo = $exe->getPdo();
            $pdo->beginTransaction();
            try {
                foreach ($arr as $res) {
                    if (gettype($res) !== 'object') {
                        throw new ErrorException();
                    }
                    $res = (bool)$res();
                    if (!$res) {
                        throw new ErrorException();
                    }
                }
                $pdo->commit();
                return true;
            } catch (PDOException $e) {
                $pdo->rollBack();
                throw $e;
            } catch (ErrorException $e) {
                $pdo->rollBack();
                throw $e;
            }
        };
    }

    /**
     * @param string $sql
     * @param array $data
     * @return mixed
     */
    public function getObjArrBySql(string $sql, $data=[])
    {
        return function (IMysqlExecute $exe) use($sql) {
            $pdo = $exe->getPdo();
            $this->setLastSql($sql);
            $m_stmt = $pdo->prepare($sql);
            if (!$m_stmt) {
                Util::logD($sql);
                Util::logObj($pdo->errorInfo());
                throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
            }
            $m_stmt->execute();
            $res = $m_stmt->fetchAll(PDO::FETCH_ASSOC);
            $m_stmt->closeCursor();
            return $res;
        };
    }

    public function getObjBySql(string $sql, $data=[])
    {
        return function (IMysqlExecute $exe) use($sql) {
            $pdo = $exe->getPdo();
            $this->setLastSql($sql);
            $m_stmt = $pdo->prepare($sql);
            if (!$m_stmt) {
                throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
            }
            $m_stmt->execute();
            $res = $m_stmt->fetch(PDO::FETCH_ASSOC);
            if (in_array($res, [false, null])) {
                Util::logObj($res);
                LogHelper::logD($sql);
                LogHelper::logD($m_stmt->errorInfo());
            }

            $m_stmt->closeCursor();
            return $res;
        };
    }

    public function updateObj($db_name, $data = [], $where = [])
    {
        return function (IMysqlExecute $exe) use (&$db_name, &$data, &$where) {
            $pdo = $exe->getPdo();
            $str = $this->makeWhere($where);
            $arr = MysqlTable::makeSetData($data);
            $set = $arr[0];
            $data = $arr[1];
            $sql = "UPDATE $db_name $set $str";

            return $this->executeWithData($pdo, $sql, [$data, $where]);
        };
    }

    public function removeObj($db_name, $where = [])
    {
        return function (IMysqlExecute $exe) use (&$db_name, &$where) {
            $pdo = $exe->getPdo();
            $str = $this->makeWhere($where);
            $sql = "DELETE FROM $db_name $str";
            return $this->executeWithData($pdo, $sql, $where);
        };
    }

    public function replaceObj($db_name, $data = [])
    {
        return function (IMysqlExecute $exe) use (&$db_name, &$data) {
            $pdo = $exe->getPdo();
            $arr = MysqlTable::makeSetData($data);
            $set = $arr[0];
            $data = $arr[1];
            return $this->executeWithData($pdo, "REPLACE INTO $db_name $set", $data);
        };
    }

    public function addObj($db_name, $data = [])
    {
        return function (IMysqlExecute $exe) use (&$db_name, &$data) {
            $pdo = $exe->getPdo();
            $arr = MysqlTable::makeSetData($data);
            $str = $arr[0];
            $data = $arr[1];
            $sql = "INSERT INTO $db_name $str";

            $res = $this->executeWithData($pdo, $sql, $data);
            if ($res) {
                $last_id = (int)$pdo->lastInsertId();
                return $last_id;
            } else {
                Util::logObj($pdo->errorCode());
                Util::logObj($pdo->errorInfo());
                $this->setLastErrInfo(json_encode($pdo->errorInfo()));
                $this->setLastErrCode($pdo->errorCode());
                return $res;
            }
        };
    }

    public function getObjCount($db_name, $where = [])
    {
        return  function (IMysqlExecute $exe) use (&$db_name, &$where) {
            $pdo = $exe->getPdo();
            $str = $this->makeWhere($where);
            $_sql = "SELECT count(*) FROM $db_name $str";
            $m_stmt = $pdo->prepare($_sql);
            if (!$m_stmt) {
                throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
            }
            $this->bindParams($m_stmt, $where);
            $m_stmt->execute();
            $res = $m_stmt->fetch(PDO::FETCH_ASSOC);
            $m_stmt->closeCursor();
            if (!is_array($res) || !$res) {
                return 0;
            } else {
                return array_values($res)[0];
            }
        };
    }

    public function getObjArr($db_name, $need = [], $where = [], $group = '', $sort = '', $limit = '')
    {
        return function (IMysqlExecute $exe) use (&$db_name, &$need, &$where, &$group, &$sort, &$limit) {
            $pdo = $exe->getPdo();
            $sql = "SELECT {$this->makeNeed($need)} FROM $db_name {$this->makeWhere($where)} {$this->makeGroup($group)} {$this->makeOrder($sort)} {$this->makeLimit($limit)}";
            $this->setLastSql($sql);
            $m_stmt = $pdo->prepare($sql);
            if (!$m_stmt) throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
            if ($where) $this->bindParams($m_stmt, $where);
            $m_stmt->execute();
            $res = $m_stmt->fetchAll(PDO::FETCH_ASSOC);
            $m_stmt->closeCursor();
            $this->setLastParams([
                "where"=> $where,
                "need"=> $need,
                "group"=> $group,
                "sort"=> $sort,
                "limit"=> $limit,
            ]);

            if ($res === false && $pdo->errorCode() != "00000") {
                $this->setLastErrInfo(json_encode($pdo->errorInfo()));
                $this->setLastErrCode($pdo->errorCode());
                LogHelper::logE("sql=$sql;last_err=" . $this->getLastErrInfo());
            }
            if (!is_array($res) || !$res) {
                return [];
            } else {
                return $res;
            }
        };
    }

    public function getObjArrV1(&$rows, string $db_name, $select, array $where = [], string $group = '', $sort = '', array $limit = []): callable
    {
        return function (IMysqlExecute $exe) use (&$rows, &$db_name, &$select, &$where, &$group, &$sort, &$limit): void {
            $pdo = $exe->getPdo();
            $sql = "SELECT {$this->makeNeedV1($select)} FROM $db_name {$this->makeWhere($where)} {$this->makeGroup($group)} {$this->makeOrder($sort)} {$this->makeLimitV1($limit)}";
            $m_stmt = $pdo->prepare($sql);
            if (!$m_stmt) throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
            if ($where) $this->bindParams($m_stmt, $where);
            $m_stmt->execute();
            $rows = $m_stmt->fetchAll(PDO::FETCH_ASSOC);
            $m_stmt->closeCursor();
            $this->setLastSql($sql);
            $this->setLastParams([
                "where"=> $where,
                "need"=> $select,
                "group"=> $group,
                "sort"=> $sort,
                "limit"=> $limit,
            ]);

            if ($rows === false && $pdo->errorCode() != "00000") {
                $this->setLastErrInfo(json_encode($pdo->errorInfo()));
                $this->setLastErrCode($pdo->errorCode());
                LogHelper::logE("sql=$sql;last_err=" . $this->getLastErrInfo());
            }
            if (!is_array($rows) || !$rows) {
                $rows = [];
            }
        };
    }

    public function getObj($db_name, $need = [], $where = [], $group = '', $sort = '')
    {
        return function (IMysqlExecute $exe) use (&$db_name, &$need, &$where, &$group, &$sort) {
            $pdo = $exe->getPdo();
            $_sql = "SELECT {$this->makeNeed($need)} FROM $db_name {$this->makeWhere($where)} {$this->makeGroup($group)} {$this->makeOrder($sort)} limit 1";
            $this->setLastSql($_sql);
            $m_stmt = $pdo->prepare($_sql);
            if (!$m_stmt) throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
            if ($where) $this->bindParams($m_stmt, $where);
            $m_stmt->execute();
            $res = $m_stmt->fetch(PDO::FETCH_ASSOC);
            $m_stmt->closeCursor();

            $this->setLastParams([
                "where"=> $where,
                "need"=> $need,
                "group"=> $group,
                "sort"=> $sort,
            ]);


            if ($res === false && $pdo->errorCode() != "00000") {
                $this->setLastErrInfo(json_encode($pdo->errorInfo()));
                $this->setLastErrCode($pdo->errorCode());
                LogHelper::logE("sql=$_sql;last_err=" . $this->getLastErrInfo());
            }
            if (!is_array($res) || !$res) {
                return [];
            } else {
                return $res;
            }
        };
    }

    public function getObjV1(&$row, $db_name, $select, array $where = [], $group = '', $sort = ''): callable
    {
        return function (IMysqlExecute $exe) use (&$row, &$db_name, &$select, &$where, &$group, &$sort): void {
            $pdo = $exe->getPdo();
            $_sql = "SELECT {$this->makeNeedV1($select)} FROM $db_name {$this->makeWhere($where)} {$this->makeGroup($group)} {$this->makeOrder($sort)} limit 1";
            $m_stmt = $pdo->prepare($_sql);
            if (!$m_stmt) throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
            if (is_array($where)) $this->bindParams($m_stmt, $where);
            $m_stmt->execute();
            $row = $m_stmt->fetch(PDO::FETCH_ASSOC);
            $m_stmt->closeCursor();
            $this->setLastSql($_sql);
            $this->setLastParams([
                "where"=> $where,
                "need"=> $select,
                "group"=> $group,
                "sort"=> $sort,
            ]);


            if ($row === false && $pdo->errorCode() != "00000") {
                $this->setLastErrInfo(json_encode($pdo->errorInfo()));
                $this->setLastErrCode($pdo->errorCode());
                LogHelper::logE("sql=$_sql;last_err=" . $this->getLastErrInfo());
            }
            if (!is_array($row) || !$row) $row = [];
        };
    }

    /**
     * @param string $_sql
     * @return mixed
     */
    public function execute(string $_sql)
    {
        return function (IMysqlExecute $exe) use($_sql) {
            $pdo = $exe->getPdo();
            $m_stmt = $pdo->prepare($_sql);
            if (!$m_stmt) {
                throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
            }
            $res = $m_stmt->execute();
            $this->setLastId($pdo->lastInsertId());
            $m_stmt->closeCursor();
            if ($res === false && $pdo->errorCode() != "00000") {
                $this->setLastErrInfo(json_encode($pdo->errorInfo()));
                $this->setLastErrCode($pdo->errorCode());
                LogHelper::logE("sql=$_sql;json=" . json_encode([
                    'code'=> $pdo->errorCode(),
                    'info'=> $pdo->errorInfo()
                ]));
            }
            $this->setLastSql($_sql);
            return $res;
        };
    }

    /**
     * @param PDO $pdo
     * @param $_sql
     * @param $data
     * @return bool
     */
    private function executeWithData(PDO $pdo, $_sql, $data)
    {
        $this->setLastSql($_sql);
        $m_stmt = $pdo->prepare($_sql);
        if (!$m_stmt) {
            throw new PDOException(json_encode($pdo->errorInfo()), $pdo->errorInfo()[1]);
        }
        $this->bindParams($m_stmt, $data);


        $ret = $m_stmt->execute();
        $m_stmt->closeCursor();
        if (!$ret) {
            Util::logObj([
                $pdo->errorCode(),
                $this->getLastErrInfo(),
                $_sql,
                $data
            ]);
        }
        if ($ret === false && $pdo->errorCode() != "00000") {
            $this->setLastErrCode($pdo->errorCode());
            $this->setLastErrInfo(json_encode($pdo->errorInfo()));
            Util::logObj([
                $pdo->errorCode(),
                $this->getLastErrInfo()
            ]);
            LogHelper::logE("sql=$_sql;last_err=" . $this->getLastErrInfo());
        }

        $this->setLastParams([
            "data"=> $data,
        ]);

        return $ret;
    }

    public function getLastSql()
    {
        return $this->last_sql;
    }

    public function getLastId()
    {
        return $this->last_id;
    }

    public function getLastErrInfo(): string
    {
        return $this->last_err_info;
    }

    public function getLastErrCode(): string
    {
        return $this->last_err_code;
    }

    public function setLastSql($sql)
    {
        $this->last_sql = $sql;
    }

    public function setLastId($id)
    {
        $this->last_id = $id;
    }

    public function setLastErrInfo(string $info)
    {
        $this->last_err_info = $info;
    }

    public function setLastErrCode($code)
    {
        $this->last_err_code = $code;
    }

    public function getLastParams(): array
    {
        return $this->last_params;
    }

    public function setLastParams($params)
    {
        $this->last_params = $params;
    }

    public function clear()
    {
        CMysqlMgr::clear(true);
    }
}
